package mygame;

import com.jme3.asset.plugins.ZipLocator;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Sphere;
import com.jme3.util.SkyFactory;
import constrainedcamera.ConstrainedSimpleApplication;
import controls.EnemyControl;
import controls.TargetControl;
import java.util.Random;

/**
 * Example 9 - How to make walls and floors solid. This collision code uses Physics and a custom Action Listener.
 *
 * @author normen, with edits by Zathras
 */
public class Main extends ConstrainedSimpleApplication
        implements ActionListener {

    public static final int PLAYER_DAMAGE = 30;
    private static final int MAX_ENEMIES = 20;
    private Spatial sceneModel;
    private BulletAppState bulletAppState;
    private RigidBodyControl landscape;
    private CharacterControl player;
    private Vector3f walkDirection = new Vector3f();
    private boolean left = false, right = false, up = false, down = false;
    private GuiManager guiManager;
    private Node gunNode;
    private Sound sound;
    private Spatial target;
    //Temporary vectors used on each frame.
    //They here to avoid instanciating new vectors on each frame
    private Vector3f camDir = new Vector3f();
    private Vector3f camLeft = new Vector3f();
    //Enemy related variables
    private long enemySpawnCooldown = 0;
    private float enemySpawnChance = 80;
    private Node enemyNode;
    //util
    Random random = new Random();

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    public void simpleInitApp() {
        sound = new Sound(assetManager);


        //set flycam settings
        flyCam.setMaxLookDown(-0.98f);
        flyCam.setMaxLookUp(0.98f);

        /**
         * Set up Physics
         */
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);

        // We re-use the flyby camera for rotation, while positioning is handled by physics
        viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
        flyCam.setMoveSpeed(100);

        // set up the enemyNode
        enemyNode = new Node("enemies");
        rootNode.attachChild(enemyNode);

        setUpKeys();
        setUpLight();
        setUpScene();

//        // We load the scene from the zip file and adjust its size.
//        assetManager.registerLocator("town.zip", ZipLocator.class);
//        sceneModel = assetManager.loadModel("main.scene");
//        sceneModel.setLocalScale(2f);
//
//        // We set up collision detection for the scene by creating a
//        // compound collision shape and a static RigidBodyControl with mass zero.
//        CollisionShape sceneShape =
//                CollisionShapeFactory.createMeshShape((Node) sceneModel);
//        landscape = new RigidBodyControl(sceneShape, 0);
//        sceneModel.addControl(landscape);

        // We set up collision detection for the player by creating
        // a capsule collision shape and a CharacterControl.
        // The CharacterControl offers extra settings for
        // size, stepheight, jumping, falling, and gravity.
        // We also put the player in its starting position.
        CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
        player = new CharacterControl(capsuleShape, 0.05f);
        player.setJumpSpeed(20);
        player.setFallSpeed(30);
        player.setGravity(50);
        player.setPhysicsLocation(new Vector3f(20, 10, 30));


        // We attach the scene and the player to the rootnode and the physics space,
        // to make them appear in the game world.
//        rootNode.attachChild(sceneModel);
//        bulletAppState.getPhysicsSpace().add(landscape);
        bulletAppState.getPhysicsSpace().add(player);
        bulletAppState.getPhysicsSpace().addCollisionListener(new EnemyTargetCollisionListener(target));

        //Setup gui/hud
        guiManager = new GuiManager(guiNode, assetManager);
        guiManager.initCrossHairs(settings);

        setUpGun();
    }

    private void setUpLight() {
        // We add light so we see the scene
        AmbientLight al = new AmbientLight();
        al.setColor(ColorRGBA.White.mult(1.3f));
        rootNode.addLight(al);

        DirectionalLight dl = new DirectionalLight();
        dl.setColor(ColorRGBA.White);
        dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
        rootNode.addLight(dl);

        /* Drop shadows */
//        final int SHADOWMAP_SIZE=1024;
//        DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3);
//        dlsr.setLight(dl);
//        viewPort.addProcessor(dlsr);
// 
//        DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3);
//        dlsf.setLight(dl);
//        dlsf.setEnabled(true);
//        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
//        fpp.addFilter(dlsf);
//        viewPort.addProcessor(fpp);
    }

    private void setUpGun() {
        Spatial gun = assetManager.loadModel("Models/weapons/Conrad_Gun/Conrad_gun.j3o");
        gun.scale(5f, 5, 5f);
        gunNode = new Node("gun");
        gunNode.attachChild(gun);
        gun.move(-1.5f, -1.8f, 3.5f);

//        CollisionShape gunShape = CollisionShapeFactory.createMeshShape((Node) gun);
//        Control gunControl = new RigidBodyControl(gunShape, 0);
//        gun.addControl(gunControl);
        rootNode.attachChild(gunNode);
    }

    private void setUpScene() {
        // We load the scene from the zip file and adjust its size.
        assetManager.registerLocator("town.zip", ZipLocator.class);
        sceneModel = assetManager.loadModel("main.scene");
        sceneModel.setLocalScale(2f);

        // We set up collision detection for the scene by creating a
        // compound collision shape and a static RigidBodyControl with mass zero.
        CollisionShape sceneShape =
                CollisionShapeFactory.createMeshShape((Node) sceneModel);
        landscape = new RigidBodyControl(sceneShape, 0);
        sceneModel.addControl(landscape);
        rootNode.attachChild(sceneModel);
        bulletAppState.getPhysicsSpace().add(landscape);

        //setup enemy target
        target = assetManager.loadModel("Scenes/objects/at-pt/Sources/ATPT_comp.j3o");
//        Material targetMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
//        targetMat.setTexture("DiffuseMap", assetManager.loadTexture("Scenes/objects/at-pt/Sources/hull013b.jpg"));
//        target.setMaterial(targetMat);
        target.setLocalTranslation(0, 7, 0);
        target.scale(5f);
        target.setUserData("hp", 100);
        //target.addControl(new TargetControl(enemyNode));
        CollisionShape targetShape = CollisionShapeFactory.createMeshShape(target);
        RigidBodyControl targetRigidControl = new RigidBodyControl(targetShape, 0);
        target.addControl(targetRigidControl);
        rootNode.attachChild(target);
        bulletAppState.getPhysicsSpace().add(targetRigidControl);

        //setup sky
        Spatial sky = SkyFactory.createSky(
                assetManager, "Textures/sky/sky1.jpg", true);
        sky.setLocalTranslation(0, 300, 0);
        rootNode.attachChild(sky);
    }

    /**
     * We over-write some navigational key mappings here, so we can add physics-controlled walking and jumping:
     */
    private void setUpKeys() {
        inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
        inputManager.addListener(new ShootingActionListener(rootNode, cam, rootNode, assetManager, sound), "Shoot");
        inputManager.addListener(this, "Left");
        inputManager.addListener(this, "Right");
        inputManager.addListener(this, "Up");
        inputManager.addListener(this, "Down");
        inputManager.addListener(this, "Jump");
    }

    /**
     * These are our custom actions triggered by key presses. We do not walk yet, we just keep track of the direction
     * the user pressed.
     */
    public void onAction(String binding, boolean isPressed, float tpf) {
        if (binding.equals("Left")) {
            left = isPressed;
        } else if (binding.equals("Right")) {
            right = isPressed;
        } else if (binding.equals("Up")) {
            up = isPressed;
        } else if (binding.equals("Down")) {
            down = isPressed;
        } else if (binding.equals("Jump")) {
            if (isPressed) {
                player.jump();
            }
        }
    }

    /**
     * This is the main event loop--walking happens here. We check in which direction the player is walking by
     * interpreting the camera direction forward (camDir) and to the side (camLeft). The setWalkDirection() command is
     * what lets a physics-controlled player walk. We also make sure here that the camera moves with player.
     */
    @Override
    public void simpleUpdate(float tpf) {
        spawnEnemies();

//        if (player.onGround()) {
        camDir.set(cam.getDirection()).multLocal(0.6f);

        camLeft.set(cam.getLeft()).multLocal(0.4f);
        walkDirection.set(0, 0, 0);
        if (left) {
            walkDirection.addLocal(camLeft);
        }
        if (right) {
            walkDirection.addLocal(camLeft.negate());
        }
        if (up) {
            walkDirection.addLocal(camDir);
        }
        if (down) {
            walkDirection.addLocal(camDir.negate());
//            }
        }
        player.setWalkDirection(walkDirection.setY(0).normalize());
        cam.setLocation(player.getPhysicsLocation());
        gunNode.setLocalRotation(cam.getRotation());
        gunNode.setLocalTranslation(cam.getLocation().add(cam.getDirection()));
//        gunNode.setLocalTranslation(0, -2, -4);
    }

    private void spawnEnemies() {
        if (System.currentTimeMillis() - enemySpawnCooldown >= 17) {
            enemySpawnCooldown = System.currentTimeMillis();

            if (enemyNode.getQuantity() < MAX_ENEMIES) {
//                if (new Random().nextInt((int) enemySpawnChance) == 0) {
                if (random.nextInt((int) enemySpawnChance) == 0) {
                    spawnEnemy();
                }
            }
            //increase Spawn Time
            if (enemySpawnChance >= 1.1f) {
                enemySpawnChance -= 0.005f;
            }
        }
    }

    private void spawnEnemy() {
        Spatial spatial = getEnemySpatial();

        Control enemyControl = new EnemyControl(assetManager);
        spatial.setLocalTranslation(getSpawnPosition());
        spatial.addControl(enemyControl);
        spatial.setUserData("hp", 100);
        //Node newEnemyNode= new Node("enemyNode");
        enemyNode.attachChild(spatial);
        System.out.println(spatial.getParent().getName());
    }

    private Vector3f getSpawnPosition() {
        Vector3f pos;
        float dist;
        do {
            pos = new Vector3f(random.nextInt(1000) - random.nextInt(2 * 1000),
                    random.nextInt(20),
                    random.nextInt(1000) - random.nextInt(2 * 1000));
            dist = pos.distanceSquared(new Vector3f(0, 3, 0));
        } while (dist < 1000 && dist > 50);
        return pos;
    }

    private Spatial getEnemySpatial() {
        Sphere sphere = new Sphere(30, 30, 2f);
        Geometry spatial = new Geometry("enemy", sphere);
        Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mark_mat.setColor("Color", ColorRGBA.randomColor());
        spatial.setMaterial(mark_mat);
       // Spatial spatial = assetManager.loadModel("Models/enemies/tronballs/blue.j3o");
        //spatial.setName("enemy");
        CollisionShape enemyShape = CollisionShapeFactory.createBoxShape(spatial);
        RigidBodyControl enemyRigidControl = new RigidBodyControl(enemyShape, 0);
        //spatial.addControl(enemyRigidControl);
        bulletAppState.getPhysicsSpace().add(enemyRigidControl);
        return spatial;
    }
}